package com.teamfractal.fracdustry.common.blockentity.Machines;

import com.mojang.datafixers.DSL;
import com.teamfractal.fracdustry.common.block.impl.machines.FDOreProcessorBlock;
import com.teamfractal.fracdustry.common.container.Machines.FDOreProcessorContainer;
import com.teamfractal.fracdustry.common.container.datasync.SimpleArray;
import com.teamfractal.fracdustry.common.data.recipes.OreProcessorRecipe;
import com.teamfractal.fracdustry.common.data.recipes.init.FDRecipeTypes;
import com.teamfractal.fracdustry.common.sound.FDSounds;
import com.teamfractal.fracdustry.common.util.energystorage.FDEnergyStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.registries.ObjectHolder;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Optional;

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class FDOreProcessorBlockEntity extends BlockEntity implements MenuProvider {
    public static final String NAME = "fracdustry:ore_processor";

    @ObjectHolder(NAME)
    public static BlockEntityType<FDOreProcessorBlockEntity> BLOCK_ENTITY_TYPE;

    private final ItemStackHandler itemHandler = createHandler();
    private final FDEnergyStorage energyStorage = createEnergy();

    private final LazyOptional<IItemHandler> handler = LazyOptional.of(() -> itemHandler);
    private final LazyOptional<FDEnergyStorage> energy = LazyOptional.of(() -> energyStorage);

    private boolean canCraft;
    private int timer;
    private final SimpleArray processNow = new SimpleArray();
    private final SimpleArray processTotal = new SimpleArray();
    private final SimpleArray currentEnergy = new SimpleArray();
    private final SimpleArray maxEnergy = new SimpleArray();


    @SubscribeEvent
    public static void onRegisterBlockEntityType(@Nonnull RegistryEvent.Register<BlockEntityType<?>> event) {
        event.getRegistry().register(BlockEntityType.Builder.
                of(FDOreProcessorBlockEntity::new, FDOreProcessorBlock.BLOCK).build(DSL.remainderType()).setRegistryName(NAME));
    }

    @Nonnull
    @Override
    public Component getDisplayName() {
        return new TextComponent("");
    }
    public AbstractContainerMenu createMenu(int windowId, @Nonnull Inventory playerInventory, @Nonnull Player playerEntity) {

        if (level != null) {
            return new FDOreProcessorContainer(windowId, level, worldPosition, playerInventory, playerEntity, processNow, processTotal, currentEnergy, maxEnergy);
        }
        return null;
    }

    //Disable capabilities when block is removed
    @Override
    public void setRemoved() {
        super.setRemoved();
        handler.invalidate();
        energy.invalidate();
    }

    public boolean canProcess(@Nullable Optional<OreProcessorRecipe> recipeIn){
        if (recipeIn != null) {
            if (!this.itemHandler.getStackInSlot(0).isEmpty() && recipeIn.isPresent()) {
                ItemStack itemstack = recipeIn.get().getResultItem();
                if (itemstack.isEmpty()) {
                    return false;
                } else {
                    ItemStack itemstack1 = this.itemHandler.getStackInSlot(1);
                    if (itemstack1.isEmpty()) {
                        return true;
                    } else if (!itemstack1.sameItem(itemstack)) {
                        return false;
                    } else if (itemstack1.getCount() + itemstack.getCount() <= 64 && itemstack1.getCount() + itemstack.getCount() <= itemstack1.getMaxStackSize()) {
                        return true;
                    } else {
                        return itemstack1.getCount() + itemstack.getCount() <= itemstack.getMaxStackSize();
                    }
                }
            } else {
                return false;
            }
        }
        return false;
    }
    //Block's ore process and sound playing
    public void tickServer(BlockState state) {
        SimpleContainer inv = new SimpleContainer(itemHandler.getSlots());
        for (int i = 0; i < itemHandler.getSlots(); i++) {
            inv.setItem(i, itemHandler.getStackInSlot(i));
        }
        Optional<OreProcessorRecipe> recipe = level != null ? level.getRecipeManager()
                .getRecipeFor(FDRecipeTypes.recipeOreProcessor, inv, level) : Optional.empty();
        int t = OreProcessorRecipe.getProcess();
        if (!canProcess(recipe)){
            canCraft = false;
            timer = 0;
            setChanged();
        }

        if (timer <= 0) {

            if (canCraft && canProcess(recipe)){
                craft();
                canCraft = false;
                setChanged();
            }


            if (t > 0 && recipe.isPresent()) {
                timer = t;
                canCraft = true;
                setChanged();
            }
        }
        if (timer > 0 && energyStorage.getEnergyStored() > 5) {
            processNow.set(0,timer);
            timer--;
            //todo:make energy consumption adjustable in configs
            energyStorage.consumeEnergy(5);
            setChanged();
            if (level != null && level.getGameTime() % 20 == 0 && canProcess(recipe)) {
                level.playSound(null, worldPosition, FDSounds.ore_processor_loop.get(), SoundSource.BLOCKS, 0.2f, 1);
            }
        }
        setChanged();
        processTotal.set(0,OreProcessorRecipe.getProcess());
        currentEnergy.set(0,energyStorage.getEnergyStored());
        maxEnergy.set(0,energyStorage.getMaxEnergyStored());



        //Set block's property "powered" true or false decided by whether it's processing any ore
        BlockState blockState = null;
        if (level != null) {
            blockState = level.getBlockState(worldPosition);
        }
        boolean isPowered = timer > 0 && energyStorage.getEnergyStored() > 5 && canCraft;
        if (blockState != null && blockState.getValue(BlockStateProperties.POWERED) != isPowered) {
            level.setBlock(worldPosition, blockState.setValue(BlockStateProperties.POWERED, isPowered),
                    3);
        }
    }



    public FDOreProcessorBlockEntity(BlockPos pos, BlockState state)
    {
        super(BLOCK_ENTITY_TYPE,pos,state);
    }

    //NBT saving and loading
    @Nonnull
    @Override
    public CompoundTag save(@Nonnull  CompoundTag tag) {
        tag.put("inv", itemHandler.serializeNBT());
        energy.ifPresent(h -> h.serializeNBT(tag));
        tag.putInt("timer", timer);
        return super.save(tag);
    }

    @Override
    public void load(@Nonnull CompoundTag tag) {
        itemHandler.deserializeNBT(tag.getCompound("inv"));
        energy.ifPresent(h -> h.deserializeNBT(tag));
        timer = tag.getInt("timer");
        super.load(tag);
    }

    //ItemStack Handler
    private ItemStackHandler createHandler() {
        return new ItemStackHandler(2) {

            @Override
            protected void onContentsChanged(int slot) {
                setChanged();
            }

            @Override
            public boolean isItemValid(int slot, @Nonnull ItemStack stack) {
                return true;
            }

        };
    }

    private FDEnergyStorage createEnergy() {
        return new FDEnergyStorage(50000, 500) {
            @Override
            protected void onEnergyChanged() {
                setChanged();
            }

        };
    }

    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
        if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
            boolean isValid = (side == Direction.UP);
            return isValid?handler.cast():super.getCapability(cap, side);
        }
        if (cap == CapabilityEnergy.ENERGY) {
            boolean isValid = side != null && side.getAxis().isHorizontal()
                    && side.getOpposite() == this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING);
            return isValid?energy.cast():super.getCapability(cap, side);
        }
        return super.getCapability(cap, side);
    }

    public void craft() {
        SimpleContainer inv = new SimpleContainer(itemHandler.getSlots());
        for (int i = 0; i < itemHandler.getSlots(); i++) {
            inv.setItem(i, itemHandler.getStackInSlot(i));
        }

        Optional<OreProcessorRecipe> recipe = level != null ? level.getRecipeManager()
                .getRecipeFor(FDRecipeTypes.recipeOreProcessor, inv, level) : Optional.empty();

        recipe.ifPresent(iRecipe -> {
            ItemStack output = iRecipe.getResultItem();
            craftTheItem(output);
            setChanged();
        });
    }

    private void craftTheItem(ItemStack output) {
        itemHandler.extractItem(0, 1, false);
        itemHandler.insertItem(1, output, false);
    }
}

